using System;
using Fadd.Globalization;
#if TEST
using Xunit;
#endif

namespace Fadd.Validation
{
    /// <summary>
    /// Checks if a primitive type is between min and max.
    /// </summary>
    /// <para>
    /// Language file items:
    /// <list type="table">
    ///     <listheader>
    ///         <term>ItemName</term>
    ///         <description>Language text</description>
    ///     </listheader>
    ///     <item>
    ///         <term>Between</term>
    ///         <description>'{0}' must be between {1} and {2}.</description>
    ///     </item>
    ///     <item>
    ///         <term>BetweenString</term>
    ///         <description>'{0}' may only have {1} up to {2} letters.</description>
    ///     </item>
    ///	  <item>
    ///		  <term>BetweenStringLength</term>
    ///          <description>'{0}' must be {1} letters long.</description>
    ///      </item>
    /// </list>
    /// </para>
    public class ValidateBetweenAttribute : ValidateAttribute
    {
        private readonly object _min;
        private readonly object _max;
        private object _value;

        /// <summary>
        /// Initializes a new instance of the <see cref="ValidateAttribute"/> class.
        /// </summary>
        /// <param name="min">Value must be equal to this or larger.</param>
        /// <param name="max">Value must be equal or less than this.</param>
        /// <exception cref="ArgumentException">Thrown if the max value is more than the min value</exception>
        public ValidateBetweenAttribute(object min, object max)
        {
            IComparable comparable = (IComparable) min;
            if (comparable.CompareTo(max) > 0)
                throw new ArgumentException("max cannot be less than min.");
            _min = min;
            _max = max;
        }


        /// <summary>
        /// Min value
        /// </summary>
        public object Min
        {
            get { return _min; }
        }

        /// <summary>
        /// Max value
        /// </summary>
        public object Max
        {
            get { return _max; }
        }

        /// <summary>
        /// Validate value
        /// </summary>
        /// <param name="context">Can be used to send a context object to the validation class. Useful if you provide your own validation classes which need to get information from your application. <seealso cref="BeforeValidationEventArgs"/></param>
        /// <param name="value">value to validate</param>
        /// <returns>
        /// true if value passed the validation; otherwise false.
        /// </returns>
        /// <exception cref="NotSupportedException">Thrown if an unsupported object value is supplied</exception>
        public override bool Validate(object context, object value)
        {
            if (IsEmpty(value))
                return true;

            if (value is string)
            {
                _value = value;
                string s = (string)value;
                return s.Length >= (int)Min && s.Length <= (int)Max;
            }

            IComparable comparable = value as IComparable;
            if (comparable != null)
                return comparable.CompareTo(Max) <= 0 && comparable.CompareTo(Min) >= 0;


            throw new NotSupportedException(GetType() + " do not support " + value.GetType());
        }

        /// <summary>
        /// Localize error if validation failed.
        /// </summary>
        /// <param name="fieldName">Localized field name.</param>
        /// <param name="validationLanguage">Language node with all validation translations.</param>
        /// <returns>A localized error message if the validation failed; otherwise null.</returns>
        /// <example>
        /// <code>
        /// attribute.Localize("FirstName", "'{0}' is required"); 
        /// // will return "'{0}' is required" if the validation failed, else null.
        /// </code>
        /// </example>
        public override string Format(string fieldName, ILanguageNode validationLanguage)
        {
            if (_value is string)
				return string.Format(validationLanguage[_min != _max ? "BetweenString" : "BetweenStringLength"], fieldName, _min, _max);

            return string.Format(validationLanguage["Between"], fieldName, _min, _max);
        }

        /// <summary>
        /// Determines if the validation class support the specified type.
        /// </summary>
        /// <param name="type">Property/Value type.</param>
        /// <returns>true if the type is supported.</returns>
        public override bool SupportsType(Type type)
        {
            return type is IComparable;
        }


#if TEST
        [Fact]
        private static void Test()
        {
            ValidateBetweenAttribute attribute = new ValidateBetweenAttribute(5, 10);
            Assert.True(attribute.Validate(null, 5));
            Assert.True(attribute.Validate(null, 6));
            Assert.True(attribute.Validate(null, 7));
            Assert.True(attribute.Validate(null, 8));
            Assert.True(attribute.Validate(null, 9));
            Assert.True(attribute.Validate(null, 10));
            Assert.False(attribute.Validate(null, 4));
            Assert.False(attribute.Validate(null, 11));

            attribute = new ValidateBetweenAttribute(5.0, 10.0);
            Assert.False(attribute.Validate(null, 4.99999));
            Assert.False(attribute.Validate(null, 10.00001));

        }
#endif
    }
}
